/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: uds_transport.c
 *
 * Purpose: implementation implementation unix domain socket transport(dialer and listener) ops for cbus
 *
 * Developer:
 *   wen.gu , 2022-02-16
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#ifdef LINUX_ENABLE_TRANSPORT_IPC
#include "uds_transport.h"

#include <stddef.h>

#include "cbus/core/cbus_worker.h"
#include "cbus/core/cbus_map.h"

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define LISTENER_RECEIVE_BUF_SIZE (1024 * 16)
#define UDS_PATH_MAX 128
#define LISTENER_SELECT_TIME_OUT_SEC 10
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef struct _uds_listener
{
    socket_t socket_id;
}uds_listener_t;

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
static GErrc listener_socket_create_and_bind(uds_listener_t* listener, const GSTR url)
{
  if (listener->socket_id != INVALID_SOCKET)
    {
        LOGE("serivce socket already exist, please destroy old one before create operation\n");
        return G_ErrInvalidStatus;
    }
    if ((listener->socket_id = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    {
        LOGE("create AF_UNIX socket failed(%s)\n", strerror(errno));
        listener->socket_id = INVALID_SOCKET;
        return G_ErrInsufficientResources;
    }

    struct sockaddr_un srv_addr;
    memset(&srv_addr, 0, sizeof(srv_addr));            /* fill socket address structure with our address */
    srv_addr.sun_family = AF_UNIX;

    

    snprintf(srv_addr.sun_path, sizeof(srv_addr.sun_path), "%s", url);

    LOGI("service addr:%s, len:%zd\n", srv_addr.sun_path, sizeof(srv_addr));
    unlink(url);
    int len = offsetof(struct sockaddr_un, sun_path) + strlen(srv_addr.sun_path);

    int32_t ret = bind(listener->socket_id, (struct sockaddr*) &srv_addr, len);
    if (ret == SOCKET_ERROR)
    {
        LOGE("bind service socket:%s, failed: %s\n", srv_addr.sun_path, strerror(errno));
        close(listener->socket_id);
        listener->socket_id = INVALID_SOCKET;
        return G_ErrUndefined;
    }

    ret = listen(listener->socket_id, MAX_CLIENT_CONNECT_LIMIT);

    if (ret < 0)
    {
        LOGE("listen socket:%s, failed:%s\n", srv_addr.sun_path, strerror(errno));
        close(listener->socket_id);
        listener->socket_id = INVALID_SOCKET;
        return G_ErrUndefined;
    }

    return G_OK;
}

static GErrc listener_socket_destroy(uds_listener_t* listener)
{
    if (listener->socket_id != INVALID_SOCKET)
    {
        close(listener->socket_id);
        listener->socket_id = INVALID_SOCKET;
    }

    return G_OK;
}

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
/**
 * @brief create and initialize a listener instance
 * @param handle [out] the instance of listener(service)
 * 
 * @return GErrc, success: G_OK, else error code.
 * 
 */
GErrc uds_listener_initialize(GPHD* handle)
{
    if (!handle)
    {
        return G_ErrBadParameter;
    }

    uds_listener_t* ul = (uds_listener_t*)malloc(sizeof(uds_listener_t));

    if (!ul)
    {
        return G_ErrInsufficientResources;
    }

    ul->socket_id = INVALID_SOCKET;

    *handle = (GPHD)ul;

    return G_OK;
}

/**
 * @brief destroy a listener instance 
 * @param handle [in] the instance handle of listener
 * @return GErrc, success: G_OK, else error code.
 *  
 */
GErrc uds_listener_finish(GPHD handle)
{
    if (!handle)
    {
        return G_ErrBadParameter;
    }

    listener_socket_destroy((uds_listener_t*)handle);
    free(handle);

    //do nothing
    return G_OK;
}

/**
 * @brief offer a listener(service) with url address 
 * @param handle [in] the instance handle of listener
 * @param url [in] the url address of a listener(service)
 * @return GErrc, success: G_OK, else error code.
 */
GErrc uds_listener_bind(GPHD handle, GSTR url)
{
    if (!handle || !url || (strlen(url) <= UDS_TRANSPORT_URL_PREFIX_LEN)) /** url len must > UDS_TRANSPORT_URL_PREFIX_LEN */
    {
        return G_ErrBadParameter;
    }

    uds_listener_t* listener = (uds_listener_t*)handle;
    return listener_socket_create_and_bind(listener, url + UDS_TRANSPORT_URL_PREFIX_LEN);
}

/**
 * @brief stop a listner(service)
 * @param handle [in] the instance handle of listener
 * @return GErrc, success: G_OK, else error code.
 */
GErrc uds_listener_unbind(GPHD handle)
{
    if (!handle)
    {
        return G_ErrBadParameter;
    }

    return listener_socket_destroy((uds_listener_t*)handle);
}

/**
 * @brief accept a connected dialer
 * @param handle [in] the instance handle of listener
 * @param dialer_id [out] the id of dialer which connected to current listener
 * @param timeout_ms [in] the time of timeout in milli-second, if not accept a dialer in 'timeout_ms', then return G_ErrTimeout, 
 *                        CBUS_TRANSPORT_ACCEPT_WAIT_FOREVER for wait forever
 * @return GErrc, success: G_OK, else error code.
 * 
 */
GErrc uds_listener_accept(GPHD handle, GPHD* pipe_handle, GU32 timeout_ms)
{
    if (!handle || !pipe_handle)
    {
        return G_ErrBadParameter;
    } 

    uds_listener_t* listener = (uds_listener_t*)handle;

    if (listener->socket_id == INVALID_SOCKET)
    {
        return G_ErrInvalidStatus;
    }

    fd_set read_set;
    int ret = 0;
    struct sockaddr_un un_client;
    int len = sizeof(un_client);
    socket_t cli_fd = -1;

    FD_ZERO(&read_set);    
    FD_SET(listener->socket_id, &read_set);

    if (timeout_ms == CBUS_TRANSPORT_ACCEPT_WAIT_FOREVER)
    {
        ret = select(listener->socket_id + 1, &read_set, NULL,  NULL, NULL);
    }
    else
    {
        struct timeval timeout;  //timeout time    
        timeout.tv_sec = timeout_ms / 1000;   /** */
        timeout.tv_usec = (timeout_ms % 1000) * 1000;    
        ret = select(listener->socket_id + 1, &read_set, NULL,  NULL, &timeout);
    }
    
    if (ret > 0 && FD_ISSET(listener->socket_id, &read_set))
    {
        if ((cli_fd = accept(listener->socket_id, (struct sockaddr*)&un_client, &len)) <= 0)
        {
            return G_ErrUndefined;
        }

        //LOGD("[%s.%d]===>: got connect request, socket id: %d\n", __FUNCTION__, __LINE__, cli_fd);
        *pipe_handle = (GPHD)cli_fd;

        return G_OK;
    }
    else if (ret == 0)
    {
        return G_ErrTimeout;
    }
    else
    {
        LOGE("uds listener select failed(%s)\n", strerror(errno));
    }

    return G_ErrUndefined;
}

#endif /** end of LINUX_ENABLE_TRANSPORT_IPC */